In the first example case, we would like to explore the development of histone H3 PTM abundances and crosstalk with age and compare between four mouse tissues.

Data was published in: Tvardovskiy, A., Schwämmle, V., Kempf, S. J., Rogowska-Wrzesinska, A., & Jensen, O. N. (2017). Accumulation of histone variant H3.3 with age is associated with profound changes in the histone methylation landscape. Nucleic Acids Research, 45(16), 9272-9289.

The code included in this document can be found as a coherent R scripts here, and more examples here.

R Session Setup

Set the working directory and import the PTM-CrossTalkMapper functions.

setwd("/path/to/CrossTalkMapper/doc/")
source("../ctm-functions/ptm-crosstalkmapper.R")

Data Preparation

The data file containing quantified PTM data we used here was downloaded from CrosstalkDB and prepared as described in the README to add the “timepoint” and “biological replicate” columns. Alternatively, a .csv file can be prepared with a similar structure.

head(read.csv("../data/mouse-tissues_ctdb_timerep_4timepoints.csv"))

The first PTM-CrossTalkMapper function prepPTMdata() imports and prepares the proteoform data for calculating PTM abundances. Data are imported from the path given to the function as the first argument. histvars = FALSE lets the function ignore individual histone variants and calculates H3 total proteoform abundances. avrepls = TRUE (the default) averages the abundances from biological replicates after 0-imputation of missing values. Finally, CrosstalkDB columns that are not relevant for the generation of crosstalk maps are eliminated, and a data frame with the adjusted data is returned.

data <- prepPTMdata(csv = "../data/mouse-tissues_ctdb_timerep_4timepoints.csv", histvars = FALSE, avrepls = TRUE)
head(data)

The cell type / tissue entries will later be used as labels in the crosstalk maps. Therefore, the long expressions in this example were shortened at this point.

data$cell.type...tissue <- gsub(".*, ", "", data$cell.type...tissue)
data$cell.type...tissue <- paste0(toupper(substr(data$cell.type...tissue, 1, 1)),
                                  substr(data$cell.type...tissue, 2, nchar(data$cell.type...tissue)))
head(data)

Calculating and Transforming PTM Abundances

calcPTMab() takes the data frame object generated by prepPTMdata() as input (first argument) and calculates and transforms individual abundances (\(p_i\) and \(p_j\)), co-occurrences (\(p_{ij}\)) and interplay scores (\(I\)) for each observed combination of two PTMs \(m_i\) and \(m_j\) and transforms the individual abundances (\(\hat{p}_i\) and \(\hat{p}_j\)). The results are returned as data frame and printed to a file called ptm-abundances.tab in the directory specified with outdir = (by default the working directory).

ptm_ab <- calcPTMab(data, outdir = "data/h3/")
head(ptm_ab)

Filtering Data and Generating PTM-Crosstalk Maps

A crosstalk map is generated by applying the function CrossTalkMap() to the PTM abundance data in ptm_ab.

Assume we would like to compare PTM abundances and crosstalk between all observed modifications (acetylation, mono-, di- and trimethylation) at the positions K9 and K27. For that purpose, all rows containing the respective positions and their PTM abundances are extracted from ptm_ab. For labels that are ordered according to the amino acid sequence, i.e. K9me1K27me1 instead of K27me1K9me1, grep the first position in ptm_ab$mi and the second position in ptm_ab$mj.

ptm_ab_pos <- ptm_ab[grepl("K9", ptm_ab$mi) & grepl("K27", ptm_ab$mj),]

The filtered dataset is passed as the first argument to CrossTalkMap(). The next three options define how the experimental variables, here tissue, time point and replicate, are organized in the plot. In this example, which uses the default arguments, individual maps are generated for each of the tissues (splitplot_by = "tissue"), all with the same ranges for abundances and interplay score. The different instances of the variable passed to connected = may be connected by lines or arrows, depending on the arguments for connect_dots = and with_arrows =. Here, these groups of data points connected by arrows represent the time series of each PTM pair (connected = "timepoint"). The data points are grouped by the variable passed to group_by =, and only connected within these groups. For this particular example, this parameter is irrelevant because the measurements for the remaining experimental variable, the replicates, have been averaged, so there is only one measurement for each PTM pair for each time point and tissue. In this case, the default argument group_by = "repl" does not have to be modified.

colcode = determines according to which parameter the data points will be color-coded, in this case \(p_j\), the abundance of PTMs at K27. which_label = can take the arguments “mj” (default) for an individual or “mimj” for the combinatorial PTM label. The plot is printed to a pdf file with the name provided to filename_string = in the directory outdir =, relative to the working directory (default is the working directory).

CrossTalkMap(ptm_ab_pos,
             splitplot_by = "tissue", connected = "timepoint", group_by = "repl",
             connect_dots = TRUE, with_arrows = TRUE, colcode = "pj", which_label = "mimj",
             filename_string = "K9-K27_all-ptms", outdir = "plots/h3/crosstalkmaps/")

The result looks pretty crowded, so let’s look at methylations only in the following. The remaining PTM pairs can be compared much easier.

ptm_ab_pos_mod <- ptm_ab_pos[grepl("me", ptm_ab_pos$mi) & grepl("me", ptm_ab_pos$mj),]
CrossTalkMap(ptm_ab_pos_mod,
             splitplot_by = "tissue", connected = "timepoint", group_by = "repl",
             connect_dots = TRUE, with_arrows = TRUE, colcode = "pj", which_label = "mimj",
             filename_string = "K9-K27_all-ptms_me", outdir = "plots/h3/crosstalkmaps/")

Considering Histone Variants

Different histone variants, such as H3.1/2 and H3.3, often show substantially different PTM patterns. To distinguish between histone variants in the dataset used above, the prepPTMdata() function has a histvars = option, which keeps the input proteoform abundances based on histone variants if set to TRUE.

histvar_data <- prepPTMdata(csv = "../data/mouse-tissues_ctdb_timerep_4timepoints.csv", histvars = TRUE, avrepls = TRUE)
histvar_data$cell.type...tissue <- gsub(".*, ", "", histvar_data$cell.type...tissue)
histvar_data$cell.type...tissue <- paste0(toupper(substr(histvar_data$cell.type...tissue, 1, 1)),
                                  substr(histvar_data$cell.type...tissue, 2, nchar(histvar_data$cell.type...tissue)))
head(histvar_data)

calcPTMab() lets us then calculate the PTM abundances in the same way as in the H3 total example above.

histvar_ptm_ab <- calcPTMab(histvar_data, outdir = "data/histvars/")
head(histvar_ptm_ab)

Filtering and calling CrossTalkMap() in the same way as previously adds the information level of histone variants and generates two crosstalk maps for each tissue, based on the number of histone variants in the hist column of histvar_ptm_ab, which prepPTMdata() took care of in the first step.

histvar_ptm_ab_pos <- histvar_ptm_ab[grepl("K9", histvar_ptm_ab$mi) & grepl("K27", histvar_ptm_ab$mj),]
histvar_ptm_ab_pos_mod <- histvar_ptm_ab_pos[grepl("me", histvar_ptm_ab_pos$mi) & grepl("me", histvar_ptm_ab_pos$mj),]
CrossTalkMap(histvar_ptm_ab_pos_mod,
             splitplot_by = "tissue", connected = "timepoint", group_by = "repl",
             connect_dots = TRUE, with_arrows = TRUE, colcode = "pj", which_label = "mimj",
             filename_string = "K9-K27_all-ptms_me", outdir = "plots/histvars/crosstalkmaps/")

LS0tCnRpdGxlOiAiUFRNLUNyb3NzVGFsa01hcHBlciIKc3VidGl0bGU6IERvY3VtZW50YXRpb24gYW5kIENvbW1vbiBVc2UgQ2FzZXMKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKSW4gdGhlIGZpcnN0IGV4YW1wbGUgY2FzZSwgd2Ugd291bGQgbGlrZSB0byBleHBsb3JlIHRoZSBkZXZlbG9wbWVudCBvZiBoaXN0b25lIEgzIFBUTSBhYnVuZGFuY2VzIGFuZCBjcm9zc3RhbGsgd2l0aCBhZ2UgYW5kIGNvbXBhcmUgYmV0d2VlbiBmb3VyIG1vdXNlIHRpc3N1ZXMuCgpEYXRhIHdhcyBwdWJsaXNoZWQgaW46IFR2YXJkb3Zza2l5LCBBLiwgU2Nod8OkbW1sZSwgVi4sIEtlbXBmLCBTLiBKLiwgUm9nb3dza2EtV3J6ZXNpbnNrYSwgQS4sICYgSmVuc2VuLCBPLiBOLiAoMjAxNykuIEFjY3VtdWxhdGlvbiBvZiBoaXN0b25lIHZhcmlhbnQgSDMuMyB3aXRoIGFnZSBpcyBhc3NvY2lhdGVkIHdpdGggcHJvZm91bmQgY2hhbmdlcyBpbiB0aGUgaGlzdG9uZSBtZXRoeWxhdGlvbiBsYW5kc2NhcGUuIE51Y2xlaWMgQWNpZHMgUmVzZWFyY2gsIDQ1KDE2KSwgOTI3Mi05Mjg5LgoKVGhlIGNvZGUgaW5jbHVkZWQgaW4gdGhpcyBkb2N1bWVudCBjYW4gYmUgZm91bmQgYXMgYSBjb2hlcmVudCBSIHNjcmlwdHMgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS92ZWl0dmVpdC9Dcm9zc1RhbGtNYXBwZXIvdHJlZS9tYXN0ZXIvZG9jL3NjcmlwdHMvKSwgYW5kIG1vcmUgZXhhbXBsZXMgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS92ZWl0dmVpdC9Dcm9zc1RhbGtNYXBwZXIvYmxvYi9tYXN0ZXIvbW91c2UtdGlzc3VlLWFuYWx5c2lzL2gzL3NjcmlwdHMvY3Jvc3N0YWxrbWFwc19oMy5SKS4KCiMjIFIgU2Vzc2lvbiBTZXR1cAoKU2V0IHRoZSB3b3JraW5nIGRpcmVjdG9yeSBhbmQgaW1wb3J0IHRoZSBQVE0tQ3Jvc3NUYWxrTWFwcGVyIGZ1bmN0aW9ucy4KCmBgYHtyLCBldmFsPUZBTFNFfQpzZXR3ZCgiL3BhdGgvdG8vQ3Jvc3NUYWxrTWFwcGVyL2RvYy8iKQpzb3VyY2UoIi4uL2N0bS1mdW5jdGlvbnMvcHRtLWNyb3NzdGFsa21hcHBlci5SIikKYGBgCgojIyBEYXRhIFByZXBhcmF0aW9uCgpUaGUgZGF0YSBmaWxlIGNvbnRhaW5pbmcgcXVhbnRpZmllZCBQVE0gZGF0YSB3ZSB1c2VkIGhlcmUgd2FzIGRvd25sb2FkZWQgZnJvbSBbQ3Jvc3N0YWxrREJdKGh0dHA6Ly9jcm9zc3RhbGtkYi5ibWIuc2R1LmRrLykgYW5kIHByZXBhcmVkIGFzIGRlc2NyaWJlZCBpbiB0aGUgW1JFQURNRV0oaHR0cHM6Ly9naXRodWIuY29tL3ZlaXR2ZWl0L0Nyb3NzVGFsa01hcHBlci90cmVlL21hc3Rlci9kYXRhKSB0byBhZGQgdGhlICJ0aW1lcG9pbnQiIGFuZCAiYmlvbG9naWNhbCByZXBsaWNhdGUiIGNvbHVtbnMuIEFsdGVybmF0aXZlbHksIGEgLmNzdiBmaWxlIGNhbiBiZSBwcmVwYXJlZCB3aXRoIGEgW3NpbWlsYXIgc3RydWN0dXJlXShodHRwczovL2dpdGh1Yi5jb20vdmVpdHZlaXQvQ3Jvc3NUYWxrTWFwcGVyI2RhdGEpLgoKYGBge3J9CmhlYWQocmVhZC5jc3YoIi4uL2RhdGEvbW91c2UtdGlzc3Vlc19jdGRiX3RpbWVyZXBfNHRpbWVwb2ludHMuY3N2IikpCmBgYAoKVGhlIGZpcnN0IFBUTS1Dcm9zc1RhbGtNYXBwZXIgZnVuY3Rpb24gYHByZXBQVE1kYXRhKClgIGltcG9ydHMgYW5kIHByZXBhcmVzIHRoZSBwcm90ZW9mb3JtIGRhdGEgZm9yIGNhbGN1bGF0aW5nIFBUTSBhYnVuZGFuY2VzLiBEYXRhIGFyZSBpbXBvcnRlZCBmcm9tIHRoZSBwYXRoIGdpdmVuIHRvIHRoZSBmdW5jdGlvbiBhcyB0aGUgZmlyc3QgYXJndW1lbnQuIGBoaXN0dmFycyA9IEZBTFNFYCBsZXRzIHRoZSBmdW5jdGlvbiBpZ25vcmUgaW5kaXZpZHVhbCBoaXN0b25lIHZhcmlhbnRzIGFuZCBjYWxjdWxhdGVzIEgzIHRvdGFsIHByb3Rlb2Zvcm0gYWJ1bmRhbmNlcy4gYGF2cmVwbHMgPSBUUlVFYCAodGhlIGRlZmF1bHQpIGF2ZXJhZ2VzIHRoZSBhYnVuZGFuY2VzIGZyb20gYmlvbG9naWNhbCByZXBsaWNhdGVzIGFmdGVyIDAtaW1wdXRhdGlvbiBvZiBtaXNzaW5nIHZhbHVlcy4gRmluYWxseSwgQ3Jvc3N0YWxrREIgY29sdW1ucyB0aGF0IGFyZSBub3QgcmVsZXZhbnQgZm9yIHRoZSBnZW5lcmF0aW9uIG9mIGNyb3NzdGFsayBtYXBzIGFyZSBlbGltaW5hdGVkLCBhbmQgYSBkYXRhIGZyYW1lIHdpdGggdGhlIGFkanVzdGVkIGRhdGEgaXMgcmV0dXJuZWQuCgpgYGB7cn0KZGF0YSA8LSBwcmVwUFRNZGF0YShjc3YgPSAiLi4vZGF0YS9tb3VzZS10aXNzdWVzX2N0ZGJfdGltZXJlcF80dGltZXBvaW50cy5jc3YiLCBoaXN0dmFycyA9IEZBTFNFLCBhdnJlcGxzID0gVFJVRSkKaGVhZChkYXRhKQpgYGAKClRoZSBjZWxsIHR5cGUgLyB0aXNzdWUgZW50cmllcyB3aWxsIGxhdGVyIGJlIHVzZWQgYXMgbGFiZWxzIGluIHRoZSBjcm9zc3RhbGsgbWFwcy4gVGhlcmVmb3JlLCB0aGUgbG9uZyBleHByZXNzaW9ucyBpbiB0aGlzIGV4YW1wbGUgd2VyZSBzaG9ydGVuZWQgYXQgdGhpcyBwb2ludC4KCmBgYHtyfQpkYXRhJGNlbGwudHlwZS4uLnRpc3N1ZSA8LSBnc3ViKCIuKiwgIiwgIiIsIGRhdGEkY2VsbC50eXBlLi4udGlzc3VlKQpkYXRhJGNlbGwudHlwZS4uLnRpc3N1ZSA8LSBwYXN0ZTAodG91cHBlcihzdWJzdHIoZGF0YSRjZWxsLnR5cGUuLi50aXNzdWUsIDEsIDEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1YnN0cihkYXRhJGNlbGwudHlwZS4uLnRpc3N1ZSwgMiwgbmNoYXIoZGF0YSRjZWxsLnR5cGUuLi50aXNzdWUpKSkKaGVhZChkYXRhKQpgYGAKCiMjIENhbGN1bGF0aW5nIGFuZCBUcmFuc2Zvcm1pbmcgUFRNIEFidW5kYW5jZXMKCmBjYWxjUFRNYWIoKWAgdGFrZXMgdGhlIGRhdGEgZnJhbWUgb2JqZWN0IGdlbmVyYXRlZCBieSBgcHJlcFBUTWRhdGEoKWAgYXMgaW5wdXQgKGZpcnN0IGFyZ3VtZW50KSBhbmQgY2FsY3VsYXRlcyBhbmQgdHJhbnNmb3JtcyBpbmRpdmlkdWFsIGFidW5kYW5jZXMgKCRwX2kkIGFuZCAkcF9qJCksIGNvLW9jY3VycmVuY2VzICgkcF97aWp9JCkgYW5kIGludGVycGxheSBzY29yZXMgKCRJJCkgZm9yIGVhY2ggb2JzZXJ2ZWQgY29tYmluYXRpb24gb2YgdHdvIFBUTXMgJG1faSQgYW5kICRtX2okIGFuZCB0cmFuc2Zvcm1zIHRoZSBpbmRpdmlkdWFsIGFidW5kYW5jZXMgKCRcaGF0e3B9X2kkIGFuZCAkXGhhdHtwfV9qJCkuIFRoZSByZXN1bHRzIGFyZSByZXR1cm5lZCBhcyBkYXRhIGZyYW1lIGFuZCBwcmludGVkIHRvIGEgZmlsZSBjYWxsZWQgYHB0bS1hYnVuZGFuY2VzLnRhYmAgaW4gdGhlIGRpcmVjdG9yeSBzcGVjaWZpZWQgd2l0aCBgb3V0ZGlyID1gIChieSBkZWZhdWx0IHRoZSB3b3JraW5nIGRpcmVjdG9yeSkuCgpgYGB7cn0KcHRtX2FiIDwtIGNhbGNQVE1hYihkYXRhLCBvdXRkaXIgPSAiZGF0YS9oMy8iKQpoZWFkKHB0bV9hYikKYGBgCgojIyBGaWx0ZXJpbmcgRGF0YSBhbmQgR2VuZXJhdGluZyBQVE0tQ3Jvc3N0YWxrIE1hcHMKCkEgY3Jvc3N0YWxrIG1hcCBpcyBnZW5lcmF0ZWQgYnkgYXBwbHlpbmcgdGhlIGZ1bmN0aW9uIGBDcm9zc1RhbGtNYXAoKWAgdG8gdGhlIFBUTSBhYnVuZGFuY2UgZGF0YSBpbiBgcHRtX2FiYC4KCkFzc3VtZSB3ZSB3b3VsZCBsaWtlIHRvIGNvbXBhcmUgUFRNIGFidW5kYW5jZXMgYW5kIGNyb3NzdGFsayBiZXR3ZWVuIGFsbCBvYnNlcnZlZCBtb2RpZmljYXRpb25zIChhY2V0eWxhdGlvbiwgbW9uby0sIGRpLSBhbmQgdHJpbWV0aHlsYXRpb24pIGF0IHRoZSBwb3NpdGlvbnMgSzkgYW5kIEsyNy4gRm9yIHRoYXQgcHVycG9zZSwgYWxsIHJvd3MgY29udGFpbmluZyB0aGUgcmVzcGVjdGl2ZSBwb3NpdGlvbnMgYW5kIHRoZWlyIFBUTSBhYnVuZGFuY2VzIGFyZSBleHRyYWN0ZWQgZnJvbSBgcHRtX2FiYC4gRm9yIGxhYmVscyB0aGF0IGFyZSBvcmRlcmVkIGFjY29yZGluZyB0byB0aGUgYW1pbm8gYWNpZCBzZXF1ZW5jZSwgaS5lLiBLOW1lMUsyN21lMSBpbnN0ZWFkIG9mIEsyN21lMUs5bWUxLCBncmVwIHRoZSBmaXJzdCBwb3NpdGlvbiBpbiBgcHRtX2FiJG1pYCBhbmQgdGhlIHNlY29uZCBwb3NpdGlvbiBpbiBgcHRtX2FiJG1qYC4KCmBgYHtyfQpwdG1fYWJfcG9zIDwtIHB0bV9hYltncmVwbCgiSzkiLCBwdG1fYWIkbWkpICYgZ3JlcGwoIksyNyIsIHB0bV9hYiRtaiksXQpgYGAKClRoZSBmaWx0ZXJlZCBkYXRhc2V0IGlzIHBhc3NlZCBhcyB0aGUgZmlyc3QgYXJndW1lbnQgdG8gYENyb3NzVGFsa01hcCgpYC4gVGhlIG5leHQgdGhyZWUgb3B0aW9ucyBkZWZpbmUgaG93IHRoZSBleHBlcmltZW50YWwgdmFyaWFibGVzLCBoZXJlIHRpc3N1ZSwgdGltZSBwb2ludCBhbmQgcmVwbGljYXRlLCBhcmUgb3JnYW5pemVkIGluIHRoZSBwbG90LiBJbiB0aGlzIGV4YW1wbGUsIHdoaWNoIHVzZXMgdGhlIGRlZmF1bHQgYXJndW1lbnRzLCBpbmRpdmlkdWFsIG1hcHMgYXJlIGdlbmVyYXRlZCBmb3IgZWFjaCBvZiB0aGUgdGlzc3VlcyAoYHNwbGl0cGxvdF9ieSA9ICJ0aXNzdWUiYCksIGFsbCB3aXRoIHRoZSBzYW1lIHJhbmdlcyBmb3IgYWJ1bmRhbmNlcyBhbmQgaW50ZXJwbGF5IHNjb3JlLiBUaGUgZGlmZmVyZW50IGluc3RhbmNlcyBvZiB0aGUgdmFyaWFibGUgcGFzc2VkIHRvIGBjb25uZWN0ZWQgPWAgbWF5IGJlIGNvbm5lY3RlZCBieSBsaW5lcyBvciBhcnJvd3MsIGRlcGVuZGluZyBvbiB0aGUgYXJndW1lbnRzIGZvciBgY29ubmVjdF9kb3RzID1gIGFuZCBgd2l0aF9hcnJvd3MgPSBgLiBIZXJlLCB0aGVzZSBncm91cHMgb2YgZGF0YSBwb2ludHMgY29ubmVjdGVkIGJ5IGFycm93cyByZXByZXNlbnQgdGhlIHRpbWUgc2VyaWVzIG9mIGVhY2ggUFRNIHBhaXIgKGBjb25uZWN0ZWQgPSAidGltZXBvaW50ImApLiBUaGUgZGF0YSBwb2ludHMgYXJlIGdyb3VwZWQgYnkgdGhlIHZhcmlhYmxlIHBhc3NlZCB0byBgZ3JvdXBfYnkgPWAsIGFuZCBvbmx5IGNvbm5lY3RlZCB3aXRoaW4gdGhlc2UgZ3JvdXBzLiBGb3IgdGhpcyBwYXJ0aWN1bGFyIGV4YW1wbGUsIHRoaXMgcGFyYW1ldGVyIGlzIGlycmVsZXZhbnQgYmVjYXVzZSB0aGUgbWVhc3VyZW1lbnRzIGZvciB0aGUgcmVtYWluaW5nIGV4cGVyaW1lbnRhbCB2YXJpYWJsZSwgdGhlIHJlcGxpY2F0ZXMsIGhhdmUgYmVlbiBhdmVyYWdlZCwgc28gdGhlcmUgaXMgb25seSBvbmUgbWVhc3VyZW1lbnQgZm9yIGVhY2ggUFRNIHBhaXIgZm9yIGVhY2ggdGltZSBwb2ludCBhbmQgdGlzc3VlLiBJbiB0aGlzIGNhc2UsIHRoZSBkZWZhdWx0IGFyZ3VtZW50IGBncm91cF9ieSA9ICJyZXBsImAgZG9lcyBub3QgaGF2ZSB0byBiZSBtb2RpZmllZC4KCmBjb2xjb2RlID1gIGRldGVybWluZXMgYWNjb3JkaW5nIHRvIHdoaWNoIHBhcmFtZXRlciB0aGUgZGF0YSBwb2ludHMgd2lsbCBiZSBjb2xvci1jb2RlZCwgaW4gdGhpcyBjYXNlICRwX2okLCB0aGUgYWJ1bmRhbmNlIG9mIFBUTXMgYXQgSzI3LiBgd2hpY2hfbGFiZWwgPWAgY2FuIHRha2UgdGhlIGFyZ3VtZW50cyAibWoiIChkZWZhdWx0KSBmb3IgYW4gaW5kaXZpZHVhbCBvciAibWltaiIgZm9yIHRoZSBjb21iaW5hdG9yaWFsIFBUTSBsYWJlbC4gVGhlIHBsb3QgaXMgcHJpbnRlZCB0byBhIHBkZiBmaWxlIHdpdGggdGhlIG5hbWUgcHJvdmlkZWQgdG8gYGZpbGVuYW1lX3N0cmluZyA9YCBpbiB0aGUgZGlyZWN0b3J5IGBvdXRkaXIgPWAsIHJlbGF0aXZlIHRvIHRoZSB3b3JraW5nIGRpcmVjdG9yeSAoZGVmYXVsdCBpcyB0aGUgd29ya2luZyBkaXJlY3RvcnkpLgoKYGBge3IsIGV2YWw9RkFMU0V9CkNyb3NzVGFsa01hcChwdG1fYWJfcG9zLAogICAgICAgICAgICAgc3BsaXRwbG90X2J5ID0gInRpc3N1ZSIsIGNvbm5lY3RlZCA9ICJ0aW1lcG9pbnQiLCBncm91cF9ieSA9ICJyZXBsIiwKICAgICAgICAgICAgIGNvbm5lY3RfZG90cyA9IFRSVUUsIHdpdGhfYXJyb3dzID0gVFJVRSwgY29sY29kZSA9ICJwaiIsIHdoaWNoX2xhYmVsID0gIm1pbWoiLAogICAgICAgICAgICAgZmlsZW5hbWVfc3RyaW5nID0gIks5LUsyN19hbGwtcHRtcyIsIG91dGRpciA9ICJwbG90cy9oMy9jcm9zc3RhbGttYXBzLyIpCmBgYAoKYGBge3IsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCIuL3Bsb3RzL2gzL2Nyb3NzdGFsa21hcHMvY3Jvc3N0YWxrbWFwX3NwbGl0YnktdGlzc3VlX0gzX0s5LUsyN19hbGwtcHRtcy5wbmciKQpgYGAKClRoZSByZXN1bHQgbG9va3MgcHJldHR5IGNyb3dkZWQsIHNvIGxldCdzIGxvb2sgYXQgbWV0aHlsYXRpb25zIG9ubHkgaW4gdGhlIGZvbGxvd2luZy4gVGhlIHJlbWFpbmluZyBQVE0gcGFpcnMgY2FuIGJlIGNvbXBhcmVkIG11Y2ggZWFzaWVyLgoKYGBge3IsIGV2YWw9RkFMU0V9CnB0bV9hYl9wb3NfbW9kIDwtIHB0bV9hYl9wb3NbZ3JlcGwoIm1lIiwgcHRtX2FiX3BvcyRtaSkgJiBncmVwbCgibWUiLCBwdG1fYWJfcG9zJG1qKSxdCkNyb3NzVGFsa01hcChwdG1fYWJfcG9zX21vZCwKICAgICAgICAgICAgIHNwbGl0cGxvdF9ieSA9ICJ0aXNzdWUiLCBjb25uZWN0ZWQgPSAidGltZXBvaW50IiwgZ3JvdXBfYnkgPSAicmVwbCIsCiAgICAgICAgICAgICBjb25uZWN0X2RvdHMgPSBUUlVFLCB3aXRoX2Fycm93cyA9IFRSVUUsIGNvbGNvZGUgPSAicGoiLCB3aGljaF9sYWJlbCA9ICJtaW1qIiwKICAgICAgICAgICAgIGZpbGVuYW1lX3N0cmluZyA9ICJLOS1LMjdfYWxsLXB0bXNfbWUiLCBvdXRkaXIgPSAicGxvdHMvaDMvY3Jvc3N0YWxrbWFwcy8iKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiLi9wbG90cy9oMy9jcm9zc3RhbGttYXBzL2Nyb3NzdGFsa21hcF9zcGxpdGJ5LXRpc3N1ZV9IM19LOS1LMjdfYWxsLXB0bXNfbWUucG5nIikKYGBgCgojIyBDb25zaWRlcmluZyBIaXN0b25lIFZhcmlhbnRzCgpEaWZmZXJlbnQgaGlzdG9uZSB2YXJpYW50cywgc3VjaCBhcyBIMy4xLzIgYW5kIEgzLjMsIG9mdGVuIHNob3cgc3Vic3RhbnRpYWxseSBkaWZmZXJlbnQgUFRNIHBhdHRlcm5zLiBUbyBkaXN0aW5ndWlzaCBiZXR3ZWVuIGhpc3RvbmUgdmFyaWFudHMgaW4gdGhlIGRhdGFzZXQgdXNlZCBhYm92ZSwgdGhlIGBwcmVwUFRNZGF0YSgpYCBmdW5jdGlvbiBoYXMgYSBgaGlzdHZhcnMgPWAgb3B0aW9uLCB3aGljaCBrZWVwcyB0aGUgaW5wdXQgcHJvdGVvZm9ybSBhYnVuZGFuY2VzIGJhc2VkIG9uIGhpc3RvbmUgdmFyaWFudHMgaWYgc2V0IHRvIGBUUlVFYC4KCmBgYHtyfQpoaXN0dmFyX2RhdGEgPC0gcHJlcFBUTWRhdGEoY3N2ID0gIi4uL2RhdGEvbW91c2UtdGlzc3Vlc19jdGRiX3RpbWVyZXBfNHRpbWVwb2ludHMuY3N2IiwgaGlzdHZhcnMgPSBUUlVFLCBhdnJlcGxzID0gVFJVRSkKaGlzdHZhcl9kYXRhJGNlbGwudHlwZS4uLnRpc3N1ZSA8LSBnc3ViKCIuKiwgIiwgIiIsIGhpc3R2YXJfZGF0YSRjZWxsLnR5cGUuLi50aXNzdWUpCmhpc3R2YXJfZGF0YSRjZWxsLnR5cGUuLi50aXNzdWUgPC0gcGFzdGUwKHRvdXBwZXIoc3Vic3RyKGhpc3R2YXJfZGF0YSRjZWxsLnR5cGUuLi50aXNzdWUsIDEsIDEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1YnN0cihoaXN0dmFyX2RhdGEkY2VsbC50eXBlLi4udGlzc3VlLCAyLCBuY2hhcihoaXN0dmFyX2RhdGEkY2VsbC50eXBlLi4udGlzc3VlKSkpCmhlYWQoaGlzdHZhcl9kYXRhKQpgYGAKCmBjYWxjUFRNYWIoKWAgbGV0cyB1cyB0aGVuIGNhbGN1bGF0ZSB0aGUgUFRNIGFidW5kYW5jZXMgaW4gdGhlIHNhbWUgd2F5IGFzIGluIHRoZSBIMyB0b3RhbCBleGFtcGxlIGFib3ZlLgoKYGBge3J9Cmhpc3R2YXJfcHRtX2FiIDwtIGNhbGNQVE1hYihoaXN0dmFyX2RhdGEsIG91dGRpciA9ICJkYXRhL2hpc3R2YXJzLyIpCmhlYWQoaGlzdHZhcl9wdG1fYWIpCmBgYAoKRmlsdGVyaW5nIGFuZCBjYWxsaW5nIGBDcm9zc1RhbGtNYXAoKWAgaW4gdGhlIHNhbWUgd2F5IGFzIHByZXZpb3VzbHkgYWRkcyB0aGUgaW5mb3JtYXRpb24gbGV2ZWwgb2YgaGlzdG9uZSB2YXJpYW50cyBhbmQgZ2VuZXJhdGVzIHR3byBjcm9zc3RhbGsgbWFwcyBmb3IgZWFjaCB0aXNzdWUsIGJhc2VkIG9uIHRoZSBudW1iZXIgb2YgaGlzdG9uZSB2YXJpYW50cyBpbiB0aGUgYGhpc3RgIGNvbHVtbiBvZiBgaGlzdHZhcl9wdG1fYWJgLCB3aGljaCBgcHJlcFBUTWRhdGEoKWAgdG9vayBjYXJlIG9mIGluIHRoZSBmaXJzdCBzdGVwLgoKYGBge3IsIGV2YWw9RkFMU0V9Cmhpc3R2YXJfcHRtX2FiX3BvcyA8LSBoaXN0dmFyX3B0bV9hYltncmVwbCgiSzkiLCBoaXN0dmFyX3B0bV9hYiRtaSkgJiBncmVwbCgiSzI3IiwgaGlzdHZhcl9wdG1fYWIkbWopLF0KaGlzdHZhcl9wdG1fYWJfcG9zX21vZCA8LSBoaXN0dmFyX3B0bV9hYl9wb3NbZ3JlcGwoIm1lIiwgaGlzdHZhcl9wdG1fYWJfcG9zJG1pKSAmIGdyZXBsKCJtZSIsIGhpc3R2YXJfcHRtX2FiX3BvcyRtaiksXQpDcm9zc1RhbGtNYXAoaGlzdHZhcl9wdG1fYWJfcG9zX21vZCwKICAgICAgICAgICAgIHNwbGl0cGxvdF9ieSA9ICJ0aXNzdWUiLCBjb25uZWN0ZWQgPSAidGltZXBvaW50IiwgZ3JvdXBfYnkgPSAicmVwbCIsCiAgICAgICAgICAgICBjb25uZWN0X2RvdHMgPSBUUlVFLCB3aXRoX2Fycm93cyA9IFRSVUUsIGNvbGNvZGUgPSAicGoiLCB3aGljaF9sYWJlbCA9ICJtaW1qIiwKICAgICAgICAgICAgIGZpbGVuYW1lX3N0cmluZyA9ICJLOS1LMjdfYWxsLXB0bXNfbWUiLCBvdXRkaXIgPSAicGxvdHMvaGlzdHZhcnMvY3Jvc3N0YWxrbWFwcy8iKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiLi9wbG90cy9oaXN0dmFycy9jcm9zc3RhbGttYXBzL2Nyb3NzdGFsa21hcF9zcGxpdGJ5LXRpc3N1ZV9oaXN0dmFyc19LOS1LMjdfYWxsLXB0bXNfbWUucG5nIikKYGBgCgpgYGB7ciwgZWNobz1GQUxTRSwgZXZhbD1GQUxTRX0KIyBwZGYgdG8gcG5nIGZvciBrbml0cjo6aW5jbHVkZV9ncmFwaGljcwpmb3IgaSBpbiAqLnBkZjsgZG8gcGRmdG9wcG0gJGkgJHtpJS5wZGZ9IC1wbmcgLXNpbmdsZWZpbGUgLXIgMzAwOyBkb25lCmBgYAoK